<template>
  <b-form
    novalidate
    class="container-fluid richform-components"
    @submit="search"
    ref="richform"
    :class="{ 'was-validated': wasValidated }"
  >
    <b-row>
      <template v-for="c in form">
        <b-col
          v-if="'button' !== c.type && 'option' !== c.type"
          :cols="c.labelCol || 1"
          :offset="c.labelOffset || 0"
          class="text-right pr-2"
          :key="c.name + 'label'"
        >
          <label :for="c.name" class="mb-4" :class="{ 'd-none': c.hide }">
            <span v-if="c.required" class="text-danger">*</span>
            {{c.label}}
          </label>
        </b-col>
        <b-col
          v-if="'button' !== c.type && 'option' !== c.type"
          :cols="c.inputCol || 2"
          :offset="c.inputOffset || 0"
          :key="c.name + 'input'"
        >
          <template v-if="'text,password,email,number,url,tel,range,color'.indexOf(c.type) > -1">
            <b-form-input
              autocomplete="off"
              v-model.trim="c.value"
              :disabled="c.disabled"
              :aria-describedby="'inputLiveFeedback' + c.name"
              :pattern="c.pattern"
              :maxlength="c.maxlength"
              :min="c.min"
              :max="c.max"
              :class="{ 'd-none': c.hide }"
              :id="c.name"
              :name="c.name"
              :type="c.type"
              :required="c.required"
              :placeholder="c.placeholder"
              :formatter="c.format"
            />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
          <template v-else-if="'time,date,datetime'.indexOf(c.type) > -1">
            <datepicker
              ref="datepicker"
              class="datepicker__custom"
              :disabled="c.disabled"
              :class="{ 'd-none': c.hide }"
              inputClass="form-control"
              v-model="c.value"
              :name="c.name"
              :required="c.required"
              :placeholder="c.placeholder"
              :format="c.config ? (c.config.format || 'YYYY-MM-DD') : 'YYYY-MM-DD'"
              :range-separator="c.config ? (c.config.rangeSeparator || '~') : '~'"
              :disabled-date="disabledDate.bind(this, c.config)"
              clearable
            />
          </template>
          <template v-else-if="'select,singleselect'.indexOf(c.type) > -1">
            <multiselect
              ref="customSelect"
              class="multi-select__custom"
              :disabled="c.disabled"
              v-model="c.value"
              :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }"
              :id="c.name"
              :name="c.name"
              :options="getOption(c)"
              :searchable="false"
              :label="c.optionName || 'text'"
              :track-by="c.optionName || 'text'"
              selectLabel
              selectedLabel
              deselectLabel
              :required="c.required"
              placeholder=""
              @input="customSelectInput(c)"
            />
          </template>
          <template v-else-if="'multiselect' === c.type">
            <multiselect
              ref="customSelect"
              class="multi-select__custom"
              :disabled="c.disabled"
              :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }"
              :id="c.name"
              :name="c.name"
              v-model="c.value"
              :options="getOption(c)"
              multiple
              :searchable="false"
              :close-on-select="false"
              :placeholder="c.placeholder"
              :label="c.optionName || 'text'"
              :track-by="c.optionName || 'text'"
              selectLabel
              selectedLabel
              deselectLabel
              :required="c.required"
              @input="customSelectInput(c)"
            />
          </template>
          <template v-else-if="'search,singlesearch'.indexOf(c.type) > -1">
            <multiselect
              ref="customSelect"
              class="multi-select__custom"
              :disabled="c.disabled"
              :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }"
              v-model="c.value"
              :id="c.name"
              :name="c.name"
              :label="c.optionName || 'text'"
              :track-by="c.optionName || 'text'"
              :placeholder="c.placeholder"
              selectLabel
              selectedLabel
              deselectLabel
              :options="getOption(c)"
              searchable
              :loading="c.isLoading"
              :max-height="600"
              :required="c.required"
              @input="customSelectInput(c)"
              @search-change="asyncFindOption(c)"
            >
              <span slot="noResult">无搜索结果</span>
            </multiselect>
          </template>
          <template v-else-if="'multisearch' === c.type">
            <multiselect
              ref="customSelect"
              class="multi-select__custom"
              :disabled="c.disabled"
              :class="{ [`multi-select-inputId__${c.name}`]: true, 'd-none': c.hide }"
              v-model="c.value"
              :id="c.name"
              :name="c.name"
              :label="c.optionName || 'text'"
              :track-by="c.optionName || 'text'"
              :placeholder="c.placeholder"
              multiple
              :close-on-select="false"
              selectLabel
              selectedLabel
              deselectLabel
              :options="getOption(c)"
              searchable
              :loading="c.isLoading"
              :max-height="600"
              :required="c.required"
              @input="customSelectInput(c)"
              @search-change="asyncFindOption(c)"
            >
              <span slot="noResult">无搜索结果</span>
            </multiselect>
          </template>
          <template v-else-if="'radios' === c.type">
            <b-form-radio-group
              class="radios-checkboxes__custom"
              :aria-describedby="'inputLiveFeedback' + c.name"
              :class="{ 'd-none': c.hide }"
              v-model="c.value"
              :disabled="c.disabled"
              :options="c.radios"
              :value-field="c.valueField || 'value'"
              :text-field="c.textField || 'text'"
              :required="c.required"
              :name="c.name"
            />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
          <template v-else-if="'checkboxes' === c.type">
            <b-form-checkbox-group
              class="radios-checkboxes__custom"
              :aria-describedby="'inputLiveFeedback' + c.name"
              :class="{ 'd-none': c.hide }"
              v-model="c.value"
              :disabled="c.disabled"
              :options="c.checkboxes"
              :value-field="c.valueField || 'value'"
              :text-field="c.textField || 'text'"
              :required="c.required"
              :name="c.name"
            />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
          <template v-else-if="'textarea' === c.type">
            <b-form-textarea
              autocomplete="off"
              :id="c.name"
              :aria-describedby="'inputLiveFeedback' + c.name"
              :maxlength="c.maxlength"
              :class="{ 'd-none': c.hide }"
              :name="c.name"
              v-model.trim="c.value"
              :disabled="c.disabled"
              :required="c.required"
              :placeholder="c.placeholder"
              :rows="c.rows || 3"
              :max-rows="c.maxRows || 6"
              :no-resize="c.noResize"
            />
            <b-form-invalid-feedback :id="'inputLiveFeedback' + c.name" v-if="c.msg">{{c.msg}}</b-form-invalid-feedback>
          </template>
        </b-col>
        <b-col
          class="button-list__custom mb-4"
          v-else-if="'button' === c.type"
          :cols="c.col || 1"
          :style="{ textAlign: c.align || 'right' }"
          :key="c.name + 'button'"
        >
          <b-button type="submit" variant="primary" :class="{ 'd-none': c.hide }">{{submitName}}</b-button>
          <b-button
            variant="danger"
            @click="reset"
            :class="{ 'd-none': c.hide }"
            v-if="form.length !== 0"
          >重置</b-button>
          <b-button
            v-for="(b, index) in c.buttons"
            :class="{ 'd-none': b.hide }"
            :variant="b.variant || 'secondary'"
            :key="index"
            @click="b.click"
          >
            <i v-if="b.icon" :class="`mr-2 fa fa-${b.icon}`"></i>
            {{b.name}}
          </b-button>
        </b-col>
        <b-col
          class="button-list__custom mb-4"
          v-else-if="'option' === c.type"
          :cols="c.col || 1"
          :style="{ textAlign: c.align || 'right' }"
          :key="c.name + 'option'"
        >
          <b-button
            v-for="(b, index) in c.options"
            :class="{ 'd-none': b.hide }"
            :variant="b.variant || 'secondary'"
            :key="index"
            @click="b.click"
          >
            <i v-if="b.icon" :class="`mr-2 fa fa-${b.icon}`"></i>
            {{b.name}}
          </b-button>
        </b-col>
      </template>
      <slot></slot>
    </b-row>
  </b-form>
</template>
<script>
import $ from "jquery";
export default {
  props: {
    form: {
      type: Array,
      required: true,
      validator(value) {
        const v = value.map(item => {
          return item.name || "";
        });
        return new Set(v).size === value.length;
      }
    },
    submitName: {
      type: String,
      default: "查询"
    },
    setData: null
  },
  data() {
    return {
      wasValidated: false,
      datepicker: new Date()
    };
  },
  mounted() {
    this.initForm();
  },
  methods: {
    initForm() {
      // 用于重置表单恢复初始值
      this.formDefaultVal = this.getSubmitData();
      this.form
        .filter(
          item =>
            !!item.type &&
            (item.type.indexOf("search") > -1 ||
              item.type.indexOf("select") > -1) &&
            !!item.url &&
            Array.isArray(item.linkage)
        )
        .forEach(item => {
          item.linkage.forEach(linkage => {
            let select = this.form.find(
              item => item.name === linkage.split("-")[0]
            );
            if (!select) return;
            if (Array.isArray(select.child)) {
              if (select.child.includes(item)) {
                select.child.push(item);
              }
            } else {
              select.child = [item];
            }
          });
        });
      this.$nextTick(() => {
        this.$refs.datepicker &&
          this.$refs.datepicker.forEach(item => {
            $(item.$el)
              .find("input")
              .attr("required", $(item.$el).attr("required"))
              .attr(
                "id",
                $(item.$el)
                  .find("input")
                  .attr("name")
              )
              .attr("autocomplete", "off")
              .attr("type", "text")
              .attr("aria-required", true)
              .removeAttr("readonly");
            $(item.$el).removeAttr("required");
          });
        let list = this.form.filter(
            item =>
              !!item.type &&
              (item.type.indexOf("search") > -1 ||
                item.type.indexOf("select") > -1) &&
              !!item.url &&
              !item.likeSearch
          ),
          postList = [];
        for (let i = 0; i < list.length; i++) {
          this.$set(list[i], "isLoading", true);
          postList.push(
            this.$post(list[i].url, list[i].param || {}, { loading: false })
              .then(results => {
                results &&
                  results.status === 0 &&
                  this.$set(
                    list[i],
                    "options",
                    Array.isArray(results.data) ? results.data : []
                  );
              })
              .finally(() => {
                list[i].isLoading = false;
              })
          );
        }
        this.axios.all(postList).then(() => {
          this.$emit("mounted");
        });
      });
    },
    search(event = null) {
      if (event) {
        event.preventDefault();
        event.stopPropagation();
      }
      this.wasValidated = true;
      this.$refs.customSelect &&
        this.$refs.customSelect.forEach((item, index) => {
          if (!$(item.$el).attr("required")) {
            $(item.$el)
              .find(".multiselect__tags")
              .removeClass("invalid")
              .addClass("valid");
            return;
          }
          const id = this.$refs.customSelect[index].id,
            value = this.form.find(item => item.name == id).value;
          if (
            !!value &&
            ((typeof value === "string" && !!value) ||
              (Array.isArray(value) && value.length !== 0) ||
              (!Array.isArray(value) && typeof value === "object"))
          ) {
            $(item.$el)
              .find(".multiselect__tags")
              .removeClass("invalid")
              .addClass("valid");
          } else {
            $(item.$el)
              .find(".multiselect__tags")
              .addClass("invalid")
              .removeClass("valid");
          }
        });
      if (!this.$refs.richform.checkValidity()) return;
      if ($(".multi-select__custom .multiselect__tags.invalid").length > 0)
        return;
      this.$emit("submit", this.getSubmitData());
    },
    getSubmitData() {
      let data = {};
      this.form.forEach(item => {
        if (!item.type || item.value === undefined) return;
        if (item.value && typeof item.value === "object") {
          if (item.value.constructor === Date) {
            data[item.name] = this.formatDate(
              item.value,
              item.config ? item.config.format || "YYYY-MM-DD" : "YYYY-MM-DD"
            );
          } else if (Array.isArray(item.value)) {
            if (
              item.value.length === 2 &&
              item.value[0].constructor === Date &&
              item.value[1].constructor === Date
            ) {
              const f = item.config
                ? item.config.format || "YYYY-MM-DD"
                : "YYYY-MM-DD";
              data[item.name] = [
                this.formatDate(item.value[0], f),
                this.formatDate(item.value[1], f)
              ];
              return;
            }
            if (item.optionValue) {
              const val = item.value.reduce(
                (prev, cur) => prev + (prev ? "," : "") + cur[item.optionValue],
                ""
              );
              data[item.name] = val ? val.split(",") : [];
            } else {
              data[item.name] = JSON.parse(JSON.stringify(item.value));
            }
          } else {
            if (item.optionValue) {
              data[item.name] =
                item.value[item.optionValue] === 0
                  ? 0
                  : item.value[item.optionValue] || "";
            } else {
              data[item.name] = JSON.parse(JSON.stringify(item.value));
            }
          }
        } else {
          data[item.name] = !item.value && item.value !== 0 ? "" : item.value;
        }
      });
      return data;
    },
    disabledDate(config, time) {
      if (!config) return false;
      // time = new Date(this.formatDate(time, 'YYYY-MM-DD HH:mm:ss'))
      const max = this.form.find(item => item.name == config.maxDate_name);
      // if (max && max.value && new Date(this.formatDate(max.value, 'YYYY-MM-DD HH:mm:ss')) < time) return true
      if (max && max.value && new Date(max.value) < time) return true;
      const min = this.form.find(item => item.name == config.minDate_name);
      // if (min && min.value && new Date(this.formatDate(min.value, 'YYYY-MM-DD HH:mm:ss')) > time) return true
      if (min && min.value && new Date(min.value) > time) return true;
      // if (!!config.minDate && new Date(this.formatDate(config.minDate, 'YYYY-MM-DD HH:mm:ss')) > time) return true
      if (!!config.minDate && new Date(config.minDate) > time) return true;
      // if (!!config.maxDate && new Date(this.formatDate(config.maxDate, 'YYYY-MM-DD HH:mm:ss')) < time) return true
      if (!!config.maxDate && new Date(config.maxDate) < time) return true;
    },
    customSelectInput(select) {
      const hasVal =
        !!select.value &&
        ((Array.isArray(select.value) && select.value.length !== 0) ||
          (typeof select.value === "object" &&
            !!select.value[select.optionName || "text"]));
      Array.isArray(select.child) &&
        select.child.forEach(async child => {
          let param = child.param || {},
            key = child.linkage
              .find(item => item.startsWith(select.name))
              .split("-")[1];
          child.value = "";
          this.$set(child, "isLoading", true);
          if (hasVal) {
            if (key && key.split(":").length === 2) {
              param[key.split(":")[1]] =
                (Array.isArray(select.value)
                  ? select.value[select.value.length - 1][key.split(":")[0]]
                  : select.value[key.split(":")[0]]) || "";
            } else {
              param[key] =
                (Array.isArray(select.value)
                  ? select.value[select.value.length - 1][key]
                  : select.value[key]) || "";
            }
          } else {
            if (key && key.split(":").length === 2) {
              delete param[key.split(":")[1]];
            } else {
              delete param[key];
            }
          }
          const results = await this.$post(child.url, param, {
            loading: false
          });
          if (results && results.status === 0) {
            this.$set(
              child,
              "options",
              Array.isArray(results.data) ? results.data : []
            );
          } else {
            this.$set(child, "options", []);
          }
          child.isLoading = false;
        });
      if (!this.wasValidated) return;
      const el = $(`.multi-select-inputId__${select.name}`);
      if (!el.attr("required")) return;
      if (hasVal) {
        el.find(".multiselect__tags")
          .removeClass("invalid")
          .addClass("valid");
      } else {
        el.find(".multiselect__tags")
          .addClass("invalid")
          .removeClass("valid");
      }
    },
    reset() {
      this.wasValidated = false;
      $(".multi-select__custom .multiselect__tags")
        .removeClass("invalid")
        .removeClass("valid");
      this.form.forEach(item => {
        item.value = this.formDefaultVal[item.name];
      });
      this.$emit("update:setData", null);
    },
    // 做like查询
    async asyncFindOption(param) {
      if (
        !param.url ||
        param.type.indexOf("search") === -1 ||
        !param.likeSearch
      )
        return;
      this.$set(param, "isLoading", true);
      const results = await this.$post(param.url, {}, { loading: false });
      if (results && results.status === 0)
        this.$set(
          param,
          "options",
          Array.isArray(results.data) ? results.data : []
        );
      param.isLoading = false;
    },
    getOption(select) {
      return select.options || [];
    },
    setForm() {
      if (this.setData && this.setData.constructor === Object) {
        for (let key in this.setData) {
          let input = this.form.find(item => item.name === key);
          if (!input) continue;
          if (
            "select,singleselect,search,singlesearch".indexOf(input.type) > -1
          ) {
            if (!Array.isArray(input.options) || !input.optionValue) continue;
            input.value =
              input.options.find(
                item => item[input.optionValue] === this.setData[key]
              ) || {};
          } else if ("multiselect,multisearch".indexOf(input.type) > -1) {
            if (!Array.isArray(input.options) || !input.optionValue) continue;
            const v = Array.isArray(this.setData[key])
              ? this.setData[key].join(",")
              : this.setData[key];
            input.value = input.options.filter(item => {
              if (v.indexOf(item[input.optionValue]) > -1) return true;
            });
          } else {
            input.value = this.setData[key];
          }
        }
      } else {
        this.reset();
      }
    }
  },
  watch: {
    setData: {
      handler: function() {
        this.setForm();
      },
      deep: true
    }
  }
};
</script>
<style lang="less">
.multi-select__custom {
  min-height: calc(2.25rem + 2px);
  .multiselect__tags {
    min-height: calc(2.25rem + 2px);
    padding: 0.375rem 40px 0.375rem 0.5rem;
    border-radius: 0.2rem;
    border: 1px solid #ced4da;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
  .multiselect__select {
    height: calc(2.25rem + 1px);
    padding: 0 8px;
  }
  .multiselect__tags.invalid {
    border-color: #dc3545;
  }
  .multiselect__tags.valid {
    border-color: #ced4da;
  }
  .multiselect__single {
    padding-left: 0;
    margin-bottom: 0;
    font-size: 1rem;
    color: #495057;
    line-height: 1.5;
  }
  .multiselect__option {
    padding: 10px 0.5rem;
    min-height: 2.25rem;
    font-size: 0.875rem;
  }
  .multiselect__tag {
    display: inherit;
    padding: 2px 26px 2px 10px;
  }
  .multiselect__input {
    margin-bottom: 0;
    margin-top: 1px;
  }
  .multiselect__spinner {
    height: 1.8125rem;
  }
}

.datepicker__custom {
  width: 100%;
  input {
    height: calc(2.25rem + 2px);
    padding: 0.375rem 0.75rem;
    font-size: 1rem;
    border: 1px solid #ced4da;
    color: #495057;
  }
  .datepicker-popup {
    box-sizing: content-box;
  }
}

.button-list__custom > button:not(:last-child) {
  margin-right: 0.5rem;
}

.radios-checkboxes__custom {
  padding-top: 7px;
  label {
    padding-top: 0 !important;
  }
}

.richform-components.was-validated {
  .custom-control-input:valid ~ .custom-control-label,
  .custom-control-input.is-valid ~ .custom-control-label {
    color: #495057;
  }

  .custom-control-input:valid ~ .custom-control-label::before,
  .custom-control-input.is-valid ~ .custom-control-label::before {
    background-color: #dee2e6;
  }

  .form-control:valid,
  .form-control.is-valid,
  .custom-select:valid,
  .custom-select.is-valid {
    border: 1px solid #ced4da;
  }

  .custom-control-input:valid:checked ~ .custom-control-label::before,
  .custom-control-input.is-valid:checked ~ .custom-control-label::before {
    background-color: #007bff;
  }
}

.richform-components .invalid-feedback {
  margin-top: 0;
  line-height: 18px;
}

.richform-components > .row > div {
  padding: 0;
  label {
    padding-top: 6px; // font-size: 15px;
  }
}

// .multi-select__custom {
//   min-height: calc(1.8125rem + 2px);
//   .multiselect__tags {
//     min-height: calc(1.8125rem + 2px);
//     padding: 0.25rem 40px 0.25rem 0.5rem;
//     border-radius: 0.2rem;
//     border: 1px solid #ced4da;
//     overflow: hidden;
//     white-space: nowrap;
//     text-overflow: ellipsis;
//   }
//   .multiselect__select {
//     height: 1.8125rem;
//     padding: 0 8px;
//   }
//   .multiselect__tags.invalid {
//     border-color: #dc3545;
//   }
//   .multiselect__tags.valid {
//     border-color: #ced4da;
//   }
//   .multiselect__single {
//     padding-left: 0;
//     margin-bottom: 0;
//     font-size: 0.875rem;
//     color: #495057;
//   }
//   .multiselect__option {
//     padding: 7px 0.5rem;
//     min-height: calc(1.8125rem + 2px);
//     font-size: 0.875rem;
//   }
//   .multiselect__tag {
//     display: inherit;
//     padding: 2px 26px 2px 10px;
//   }
//   .multiselect__input {
//     margin-bottom: 0;
//     margin-top: 1px;
//   }
//   .multiselect__spinner {
//     height: 1.8125rem;
//   }
// }
// .datepicker__custom {
//   width: 100%;
//   input {
//     height: calc(1.8125rem + 2px);
//     padding: 0.25rem 0.5rem;
//     font-size: 0.875rem;
//     border: 1px solid #ced4da;
//     color: #495057;
//   }
//   .datepicker-popup {
//     box-sizing: content-box;
//   }
// }
// .button-list__custom>button:not(:last-child) {
//   margin-right: 0.5rem;
// }
// .radios-checkboxes__custom {
//   padding-top: 3px;
// }
// .richform-components.was-validated {
//   .custom-control-input:valid~.custom-control-label,
//   .custom-control-input.is-valid~.custom-control-label {
//     color: #495057;
//   }
//   .custom-control-input:valid~.custom-control-label::before,
//   .custom-control-input.is-valid~.custom-control-label::before {
//     background-color: #dee2e6;
//   }
//   .form-control:valid,
//   .form-control.is-valid,
//   .custom-select:valid,
//   .custom-select.is-valid {
//     border: 1px solid #ced4da;
//   }
//   .custom-control-input:valid:checked~.custom-control-label::before,
//   .custom-control-input.is-valid:checked~.custom-control-label::before {
//     background-color: #007bff;
//   }
// }
// .richform-components .invalid-feedback {
//   margin-top: 0;
//   line-height: 18px;
// }
// .richform-components>.row>div {
//   padding: 0;
//   label {
//     padding-top: 3px;
//     font-size: 15px;
//   }
// }
</style>